import datetime
import time
from typing import Union

from asgiref.sync import sync_to_async, async_to_sync

from license.utils.serial_utils import SerialUtil
from license.utils.theseus_tool import TheseusTool, Theseus
from common.jwt_util import JwtUtil
from license.async_task import AsyncTask

from project.settings import QCLOUD_STORAGE_OPTION, CERT_PRE_DAY, CERT_VALID_DAY

from duchy.cert_pb2 import Cert

from license.models.account import Reseller
from license.models.device import Device
from license.models.license import Serial
from license import constants as c


def serial_activate(account, machine_code: str, serial_code: str):
    """
    序列号激活
    :param account: 当前登录用户 必填
    :param machine_code: 机器码 必填
    :param serial_code: 序列码 必填
    :return:
    """
    # 根据序列码获取记录
    serial_id, table_no = SerialUtil.decode_serial_number(serial_code)
    if not serial_id:
        return c.ERROR_SERIAL_INVALID, False
    serial = Serial.sharding_get(table_no).objects.filter(id=serial_id).exclude(status=c.SERIAL_STATUS_REVOKE)
    error_code = _check_serial(serial)
    if error_code:
        return error_code, False
    # 查询device
    device = Device.objects.filter(machine_code=machine_code)
    # 判断device的服务过期时间
    if device.exists() and int(device[0].serial_expire_time.timestamp()) > int(time.time()):  # 如果还在服务期内
        return c.ERROR_DEVICE_SERVICE_NOT_EXPIRE, False
    # 激活逻辑
    """
    1. 保存设备的服务过期时间和激活时间
    2. 获取证书秘钥
    3. 生成证书
    4. 更新证书创建, 过期, 续费时间
    5. 更新或创建设备
    6. 更新序列号
    7. 客户绑定经销商信息
    8. 生成证书token并返回
    """
    # 1. 保存设备的服务过期时间和激活时间
    serial_active_time = datetime.datetime.now()
    serial_expire_time = datetime.datetime.now() + datetime.timedelta(days=serial[0].serial_valid_days)
    # 2. 获取证书秘钥
    cert_encrypt_key: Union[bytes, None] = device[0].cert_encrypt_key if device.exists() else None
    # 3. 生成证书
    cert_data, cert = _generate_certificate(
        machine_code=machine_code,
        cert_valid_time=CERT_VALID_DAY,
        cert_encrypt_key=cert_encrypt_key
    )
    if not cert_data:
        return c.ERROR_CERT_GENERATE_FAIL, False
    # 4. 更新证书创建, 过期, 续期时间
    cert_create_time = datetime.datetime.now()
    cert_expire_time = datetime.datetime.now() + datetime.timedelta(seconds=CERT_VALID_DAY)
    cert_renewal_time = datetime.datetime.now() + datetime.timedelta(seconds=(CERT_VALID_DAY - CERT_PRE_DAY))
    # 5. 更新或创建设备
    device = _update_device(
        device=device,
        serial=serial,
        account=account,
        cert=cert,
        cert_data=cert_data,
        machine_code=machine_code,
        serial_expire_time=serial_expire_time,
        serial_active_time=serial_active_time,
        cert_create_time=cert_create_time,
        cert_renewal_time=cert_renewal_time,
        cert_expire_time=cert_expire_time

    )
    # 6. 更新序列号信息
    serial = _update_serial_info(
        serial=serial[0],
        device=device,
        account=account,
        machine_code=machine_code
    )
    # 7. 客户绑定经销商信息
    account.reseller_id = serial.reseller_id
    account.reseller_name = serial.reseller_name
    account.save()
    # 8. 生成证书token并返回
    result_dict = _get_result_dict(
        cert_data=cert_data,
        cert_valid_time=CERT_VALID_DAY,
        serial_active_time=serial_active_time,
        serial_expire_time=serial_expire_time,
        cert_renewal_time=cert_renewal_time,
        cert_create_time=cert_create_time,
        cert_expire_time=cert_expire_time
    )
    return result_dict, True


def generate_ts(machine_code: str):
    """
    生成ts文件
    :param machine_code: 机器码
    :return:
    """

    try:
        device = Device.objects.get(machine_code=machine_code)
    except Device.DoesNotExist:
        return c.ERROR_MACHINE_CODE_INVALID, False
    # 解码cert证书
    error_code, flag = _decode_certificate(machine_code, device.cert_data)
    if not flag:
        return error_code, flag

    # 上传文件
    try:
        async_to_sync(AsyncTask.upload_ts_file)(machine_code, device.cert_data, device.reseller_id)
    except RuntimeError:
        return c.ERROR_TS_GENERATE_FAIL, False
    return "ts文件生成成功", True


def download_ts(machine_code: str):
    """
    下载ts文件
    :param machine_code: 机器码
    :return:
    """

    try:
        device = Device.objects.get(machine_code=machine_code)
    except Device.DoesNotExist:
        return c.ERROR_MACHINE_CODE_INVALID, False

    result_dict = {
        "ts_file_url": QCLOUD_STORAGE_OPTION['BUCKET_URL'] + device.ts_file_url,
        "ts_file_hash_value": device.ts_file_hash_value
    }

    return result_dict, True


def query_ts_status(machine_code: str):
    """
    查询ts文件状态
    # ***这个是整个ts流程的逻辑, 不是这个接口的逻辑***
    1)本地获取cos只传token
    2）异步上传ts文件到cos存储
    3）cos文件存储后，回调 biz-api/ts/notify/
        更新ts_file_status
        更新ts_file_hash
        更新ts_file_url
    4) 返回ts_file_status

    :param machine_code: 机器码
    :return:
    """

    try:
        device = Device.objects.get(machine_code=machine_code)
    except Device.DoesNotExist:
        return c.ERROR_MACHINE_CODE_INVALID, False

    result_dict = {
        "ts_file_status": device.ts_file_status
    }

    return result_dict, True


def cert_renewal(machine_code: str = None):
    """
    证书续期

    流程
    1）客户端请求，传递machine_code
    2）使用machine_code查询device设备信息
    3）查询证书cert_day，判断是否过期
        如果过期，但是没有到达服务期限，那么续期
        如果没过期，增加7天（系统settings文件中）时间
    4）生成新的证书，保存，并返回给前端

    :param machine_code: 机器码 不能为空
    :return: 
    """
    # Step 1: 根据机器码获取设备信息
    try:
        device = Device.objects.get(machine_code=machine_code)
    except Device.DoesNotExist:
        return c.ERROR_MACHINE_CODE_INVALID, False

    # Step 2: 获取旧的证书数据
    # 校验证书有效性
    args = [machine_code, device.cert_data]
    old_cert: Cert = TheseusTool.decode_cert(args)
    if not old_cert:
        return c.ERROR_MACHINE_CODE_INVALID, False

    # 边界条件：系统默认续期是7天，如果临近服务时间截止日期前1天，续期；那么仅续期1天；
    # 续期证书, 证书过期时间不能比服务时间大
    cert_valid_time = min(CERT_VALID_DAY + min(0, old_cert.expire_time - int(time.time())),
                          int(device.serial_expire_time.timestamp()) - int(time.time()))

    # Step 3: 获取新证书的过期时间, 续期时间
    expire_time, renewal_time = _get_expire_and_renewal_time(
        old_cert=old_cert,
        cert_valid_time=cert_valid_time,
        serial_expire_time=device.serial_expire_time
    )

    if not expire_time:
        return c.ERROR_SERIAL_EXPIRE, False

    # Step 4: 生成新证书
    cert_data = _get_new_cert_data(
        machine_code=machine_code,
        cert_valid_time=cert_valid_time,
        old_cert=old_cert
    )

    # Step 5: 更新设备表
    Device.objects.filter(machine_code=machine_code).update(
        cert_data=cert_data,
        cert_expire_time=expire_time,
        cert_renewal_time=renewal_time
    )

    # Step 6: 返回数据
    result_dict = _get_result_dict(
        cert_data=cert_data,
        cert_valid_time=cert_valid_time,
        serial_active_time=device.serial_active_time,
        serial_expire_time=device.serial_expire_time,
        cert_expire_time=device.cert_expire_time,
        cert_create_time=device.cert_create_time,
        cert_renewal_time=device.cert_renewal_time
    )

    return result_dict, True


def _get_expire_and_renewal_time(old_cert: Cert, serial_expire_time, cert_valid_time):
    """
    获取新证书的到期时间和续期时间
    :param old_cert: 老证书
    :param serial_expire_time:  序列号过期时间
    :param cert_valid_time: 证书有效时间
    :return:
    """
    # 如果证书过期同时服务过期, 直接返回
    if old_cert.expire_time <= int(time.time()) and int(serial_expire_time.timestamp()) <= int(time.time()):
        return None, None
    # 如果服务时间没到，那么续期返回新的证书

    # 新的到期时间
    expire_time = datetime.datetime.now() + datetime.timedelta(seconds=cert_valid_time)
    renewal_gap_time = cert_valid_time - CERT_PRE_DAY
    if renewal_gap_time <= 0:
        renewal_gap_time = cert_valid_time
    # 新的续期时间
    renewal_time = datetime.datetime.now() + datetime.timedelta(seconds=renewal_gap_time)

    return expire_time, renewal_time


def _get_new_cert_data(machine_code: str, cert_valid_time: str, old_cert: Cert):
    cert_args = [machine_code, cert_valid_time]
    # TODO 添加异常捕获机制, 增强健壮性
    cert_data = TheseusTool.renewal_cert(cert_args, old_cert)
    return cert_data


def _generate_certificate(machine_code, cert_valid_time, cert_encrypt_key):
    """
    私有方法, 生成证书
    :param machine_code: 机器码
    :param cert_valid_time: 证书有效时间
    :param cert_encrypt_key: 证书加密秘钥
    :return:
    """
    cert_args = [machine_code, str(cert_valid_time)]
    try:
        cert_data, cert = TheseusTool.encode_cert(cert_args, cert_encrypt_key)
    except Exception as e:
        return None, None
    return cert_data, cert


def _update_device(device, serial, account, machine_code, cert, cert_data,
                   serial_active_time, serial_expire_time, cert_create_time, cert_renewal_time, cert_expire_time):
    """
    私有方法, 更新设备
    :param device Device模型实例
    :param serial: Serial模型实例
    :param account: Account模型实例
    :param machine_code: 机器码
    :return:
    """
    device, _ = Device.objects.update_or_create(
        id=device[0].id if device.exists() else None,
        serial_code=serial[0].serial,
        machine_code=machine_code,
        account_id=account.id,
        account_name=account.name,
        reseller_id=serial[0].reseller_id,
        reseller_name=serial[0].reseller_name,
        cert_encrypt_key=cert.SerializeToString() if cert else None,
        cert_data=cert_data,
        serial_active_time=serial_active_time,
        serial_expire_time=serial_expire_time,
        cert_create_time=cert_create_time,
        cert_renewal_time=cert_renewal_time,
        cert_expire_time=cert_expire_time
    )
    return device


def _get_result_dict(cert_data: str, cert_valid_time: int, serial_expire_time, cert_renewal_time, cert_create_time,
                     cert_expire_time, serial_active_time):
    """
    获取结果字典
    :param cert_data: 证书数据
    :param cert_valid_time: 证书有效时间
    :param serial_expire_time: 序列号过期时间
    :param cert_renewal_time: 证书续期时间
    :param cert_create_time: 证书创建时间
    :return:
    """
    # 生成证书token
    token, payload = JwtUtil.gen_cert_token(str(hash(cert_data)), cert_valid_time - 60)
    result_dict = {
        "token": token,
        "payload": {
            'services': {
                'service_start_time': int(serial_active_time.timestamp()),
                'service_expire_time': int(serial_expire_time.timestamp())
            },
            'cert': {
                "cert_data": cert_data,
                'cert_expire_time': int(cert_expire_time.timestamp()),
                'cert_next_renewal_time': int(cert_renewal_time.timestamp()),
                'cert_create_time': int(cert_create_time.timestamp())
            }
        },
    }
    return result_dict


def _update_serial_info(serial, device, account, machine_code):
    """
    更新序列号信息
    :param serial: 序列号模型
    :param device: 设备模型
    :param account: 账户模型
    :return:
    """
    serial_status = c.SERIAL_STATUS_ACTIVATED
    if serial.serial_active_count + 1 >= c.SERIAL_ACTIVE_COUNT_MAX:
        serial_status = c.SERIAL_STATUS_REVOKE
    serial.machine_code = machine_code
    serial.device_id = device.id
    serial.account_id = account.id
    serial.account_name = account.name
    serial.status = serial_status
    serial.serial_active_count = serial.serial_active_count + 1
    serial.save()
    return serial


def _decode_certificate(machine_code: str, cert_data: str):
    """
    证书解码
    :param machine_code: 机器码, 必填
    :return:
    """
    t = Theseus(machine_code)
    cert: Cert = t.decode_cert(cert_data)
    if not cert:
        return c.ERROR_CERT_EXPIRE, False
    return cert, True


def _check_serial(serial):
    """
    校验序列号的合法性
    :param serial:
    :return:
    """
    if not serial.exists():
        return c.ERROR_SERIAL_INVALID
    reseller = Reseller.objects.filter(id=serial[0].reseller_id, status=c.RESELLER_STATUS_NORMAL)
    if not reseller.exists():
        return c.ERROR_RESELLER_FORBIDDEN
    return None
